home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Disc to the Future 2
/
Disc to the Future Part II Programmer's Reference (Wayzata Technology)(6013)(1992).bin
/
MAC
/
THINKC
/
4_0
/
TUTORIAL
/
TUTORIAL.C
Wrap
C/C++ Source or Header
|
1990-09-10
|
38KB
|
1,237 lines
/*
==============================================================================
WormMucking Screen Saver Tutorial
by Michael Pye
For use with THINK C
==============================================================================
The "WormMucking Tutorial" is a modification of the "Sunset Tutorial" I
wrote for the QuickTools/Sunset package - yet another screen saver soon to
be released. (The Sunset screen saver works very much like the After Dark
or Pyro 4.0 screen savers. There is an INIT portion, which handles all
interfacing to the Mac, and there are the individual screen saver modules.
The individual modules are loaded and called by the INIT and handle all of
the animation.) It was intended to be a tutorial on how to write your own
screen saver for use with Sunset. This version is modified to run as a
stand-alone application. The original version is a code resource that gets
loaded and called from Sunset.
The main difference is with the main() routine. Since Sunset isn't around
to load and run the screen saver, the main() routine was made to emulate
what Sunset does. That is, you can pretend that the main() routine is the
Sunset INIT that calls the WormMucking screen saver. All the rest of the code
besides main() is the WormMucking saver.
I go into detail about Sunset here and how it works because the tutorial talks
extensively about it. So in order to not have to re-write the whole tutorial
(terminally lazy), you can just pretend that whenever "Sunset" is mentioned,
for our sakes it means the "main()" routine. (Normally the main() routine
of the module would be the same routine that is named MySaver() here.)
The MySaver() routine is the MAIN routine of WormMucking.
Now that I have thouroughly screwed up this explanation and have one giant
headache, I'll shut up and get on with the good stuff.......
Writing a screen saver module is a great way to get familiar with programming
on the Macintosh. Almost all the user interface aspects are non-existant,
all you have to do is supply the animation routines. Additionally, there
are no "guidelines" you have to adhere to. Your only concerns are to make
sure your module is stable (doesn't crash) and that the animation is pleasing
to you.
The tutorial comes in two files:
1 - Tutorial.c -- This is the file you are reading right now.
It is both a tutorial on how to write a Sunset screen saver, and
source code to an actual working module.
2 - Tutorial.╣.rsrc -- This is the resource file that Tutorial.c
needs to complete the screen saver. It contains all the dialogs boxes,
version numbers, screen saver modifiers, etc. At the end of
Tutorial.c is a discussion of all the resources in this file.
When Sunset calls your module, it expects you to take one of four actions.
The requested action is passed to your module's main() routine as an integer.
The four actions are, in order of appearance: INIT, RUN, END, and CONFIG.
1 - INIT -- Your screen saver is called with this message when Sunset
initially starts. This is the first message Sunset will send to the
saver and it will only be sent once. When your saver receives this
message, the current GrafPort is set to a window that spans all
connected monitors and all screens have been blanked out. You should
do all the initialization the saver requires at this time. Typical
things done at this time are: allocating storage, determining the
dimensions of the screen, checking to see if the MacPortable is
being used, and checking to see if Color Quickdraw is available.
Additionally, you may want your saver to do some initial animation.
2 - RUN -- This is where the majority of your saver's time will be
spent. Sunset will call your saver with this message over and over
until Sunset deactivates. Each time your saver is called with this
message, it should animate a single frame of animation. You should
try to avoid doing extremely complex tasks that require a significant
amount of time. That way, the person using your saver won't have to
wait to regain control of their Macintosh.
3 - END -- When Sunset calls your saver with the END message, the
user has awakened the screen and Sunset is telling you it's time
to wrap it up. When your saver receives this message it should
perform any final actions/animation and then dispose of any storage
that was allocated.
4 - CONFIG -- This message is sent to your saver only when the person
using Sunset has clicked on the "Configure..." button in the Control
Panel cdev. This message is different from the previous three in
the following ways:
* The screen is not blanked out.
* The CONFIG message will not be preceded by the INIT message.
Your saver should present a dialog box to the user when it receives
a CONFIG message. If it is customizable, it should provide the
customize options here. If the saver isn't customizable, a simple
dialog box presenting the name of the saver will do fine.
Handling each of the messages that Sunset passes to your saver will be
discussed in more detail as they come up in the source code.
Below is the source code to the WormMucking Sunset module. Above each
function is a description of what the function does in the order it
is done. Hopefully by examining the source code to a working module,
it will help you in making screen savers of your own.
*/
/******************************************************************\
#includes & #defines
\******************************************************************/
#include "ColorToolbox.h" /* used for color worms */
#define cBaseResID 1000 /* base resource file id# */
#define cMax 32 /* max number & length of worms */
#define cNilPointer 0L /* nil */
#define cMoveToFront -1L /* brings config dialog to front */
#define OR || /* lazy */
#define AND && /* "" */
#define cNegative 0 /* used for worms movement */
#define cPositive 1
#define cOK 1 /* items 1-6 in the config dialog */
#define cCancel 2 /* plus the UserItem that surrounds */
#define cwormTally 3 /* the OK button. */
#define cWormLength 4
#define cMultiColored 5
#define cWormLinks 6
#define cUserItem 12
#define cTallyNum cBaseResID /* id#s of configurable parts */
#define cLengthNum cBaseResID+1
#define cMultiNum cBaseResID+3
#define cLinksNum cBaseResID+4
#define INIT 1 /* the four messages that Sunset will */
#define RUN 2 /* pass to the saver */
#define END 3
#define CONFIG 4
typedef int * intPtr, ** intH; /* used to get configurable resources */
/*
Define our global variables
*/
Boolean gMemoryOK, gColorQD, gMultiColored, gWormLinks;
int gWormTally, gWormLength;
int gV[cMax], gH[cMax];
int gMoveMax;
int gScreenDepth;
Rect gWormTrail[cMax][cMax], gWormRect[cMax];
Rect gScreenRect;
Point gIncentive[cMax];
GrafPtr gViewScreen;
RGBColor gWormColor, gSpawnColor[cMax];
/***************************** main *******************************\
This is the main() routine of the application. This routine
first initializes the toolbox managers. It then calls the
WormMucking module, through the MySaver() routine, to bring
up the configure dialog box.
After OK or Cancel are clicked, it sets up a window that spans
all the monitors. It then calls WormMucking, again through
the MySaver() routine, to Initialize the worms and screens.
Then it enters a loop and continually calls WormMucking,
through MySaver(), with a RUN message. WormMucking will
animate a single frame of animation every time through the
loop. When a key is pressed, or the mouse is clicked, it
exits the loop and calls WormMucking with an END message.
It then gets rid of the window covering all monitors and
sets everything back to normal before ending.
\******************************************************************/
main()
{
Boolean done=false, go=true;
WindowPtr w;
RgnHandle argn;
Str255 s;
GrafPtr oldPort;
Rect r;
SysEnvRec world;
OSErr err;
Point mypt;
EventRecord evt;
/* Initialize the toolbox managers */
MaxApplZone();
InitGraf(&thePort);
InitCursor();
InitFonts();
InitWindows();
TEInit();
InitDialogs(0L);
InitMenus();
FlushEvents(everyEvent, 0);
/* Bring up the configure dialog and allow changes */
MySaver(CONFIG);
/* Get things set up for the WormMucking module */
HideCursor();
argn=NewRgn();
s[0]=0;
/* Make the window span every monitor and black out the menu bar too */
GetPort(&oldPort);
r=(**GrayRgn).rgnBBox;
if (r.top>0)
r.top=0;
err=SysEnvirons(1,&world);
if (world.hasColorQD)
w=NewCWindow(0L,&r,&s,true,2,-1L,false,0);
else
w=NewWindow(0L,&r,&s,true,2,-1L,false,0);
/* Do this so the strip doesn't pop down in the middle of our window */
SetWRefCon(w,'worm');
RectRgn(argn,&w->portRect);
UnionRgn(argn,w->visRgn,w->visRgn);
DisposeRgn(argn);
SetPort(w);
SetPt(&mypt,0,0);
GlobalToLocal(&mypt);
SetOrigin(-mypt.h,-mypt.v);
ClipRect(&w->portRect);
/* Call the screen saver with the INIT message */
MySaver(INIT);
/* Now loop through and call the saver with the RUN message until the user does something */
FlushEvents(everyEvent,0);
ValidRect(&w->portRect);
while (go)
{
SystemTask();
MySaver(RUN);
GetNextEvent(everyEvent,&evt);
if ((evt.what==mouseDown) || (evt.what==keyDown))
go=false;
}
/* We're done, call the screen saver with the END message and that's that! */
MySaver(END);
FlushEvents(everyEvent,0);
DrawMenuBar();
DisposeWindow(w);
SetPort(oldPort);
InitCursor();
}/* main */
/**************************** MySaver *****************************\
MySaver() uses a switch statement to divide into four areas,
one for each possible value that might get passed to it:
INIT: InitializeSaver() is called to initialize all
variables and to draw the initial worms.
RUN: RUN first calls MoveWorms() to animate a single
frame of animation by moving the worms one link.
It then erases the last link in the chain and
adjusts each link's logical position.
END: END removes all events from the queue, disposes
of any storage, and sets the Pen back to normal.
CONFIG: CONFIG simply calls the ConfigureSaver() routine
to handle the customization options.
\******************************************************************/
MySaver(message)
int message;
{
int i, ii;
switch (message)
{
case INIT:
InitializeSaver();
break;
case RUN:
MoveWorms();
for ( ii = 0; ii <= gWormTally-1; ii++ )
{
ForeColor(blackColor);
if ( gWormLinks )
FrameOval( &gWormTrail[ii][gWormLength] );
else
PaintOval( &gWormTrail[ii][gWormLength] );
for ( i = gWormLength; i > 0; i-- )
gWormTrail[ii][i] = gWormTrail[ii][i-1];
gWormTrail[ii][0] = gWormRect[ii];
}
break;
case END:
FlushEvents( everyEvent,0 );
PenSize(1,1);
PenNormal();
break;
case CONFIG:
ConfigureSaver();
break;
}
}/* MySaver */
/*********************** InitializeSaver **************************\
InitializeSaver() starts by determining what environment
the saver is being run under. This is done by calling the
InitializeScreens() routine.
The resource file is then read in to get the customized
options. Before the resource file is checked however, all
options are given default values. This is in case the
resource file has been corrupted and has either faulty
or no data in it.
Finally, the worm's colors are calculated (if appropriate),
initial values and starting points are set up, and the
worms are drawn on the screen.
\******************************************************************/
InitializeSaver()
{
StringHandle tempHandle;
long temp;
int width, height, i;
intH modifier;
GrafPtr oldPort;
SysEnvRec theWorld;
GDHandle maxDevice, oldDevice;
GrafPtr viewScreen;
CGrafPtr workArea;
CGrafPort workPort;
/*
Call the InitializeScreens() routine to check to see whether
Color Quickdraw is available and to get information on the
display characteristics of all connected monitors. See the
InitializeScreens() routine for details.
*/
InitializeScreens();
/*
Set the default values for the customized variables. Then
read in the resource file to get these values.
*/
gWormTally = 15;
gWormLength = 15;
gMultiColored = 0;
gWormLinks = 0;
modifier = (intH)GetResource( 'mods', cTallyNum );
temp = (**modifier);
if ( temp >= 1 AND temp < cMax-1 )
gWormTally = temp;
modifier = (intH)GetResource( 'mods', cLengthNum );
temp = (**modifier);
if ( temp >= 1 AND temp < cMax-1 )
gWormLength = temp-1;
modifier = (intH)GetResource( 'mods', cMultiNum );
temp = (**modifier);
if ( temp >= 0 AND temp <= 1 )
gMultiColored = temp;
modifier = (intH)GetResource( 'mods', cLinksNum );
temp = (**modifier);
if ( temp >= 0 AND temp <= 1 )
gWormLinks = temp;
/*
Initialize the colors of each worm and set the "incentive"
for the worms to be nil. Then, calculate the initial
position for the worms and go through a loop to draw them
on the screen.
*/
gMoveMax = 3;
if ( gColorQD AND gScreenDepth > 3 )
for ( temp = 0; temp <= gWormTally-1; temp++ )
{
CalcRGBColor();
gSpawnColor[temp] = gWormColor;
}
for ( temp = 0; temp <= gWormTally-1; temp++ )
{
gIncentive[temp].h = 0;
gIncentive[temp].v = 0;
}
InitializeWorms();
for ( temp = gWormLength; temp >= 0; temp-- )
{
MoveWorms();
for ( i=0; i <= gWormTally-1; i++ )
{
gWormTrail[i][temp] = gWormRect[i];
gWormTrail[i][0] = gWormRect[i];
}
}
}/* InitializeSaver */
/*********************** InitializeScreens ************************\
InitializeScreens() does the following (in order):
* Checks to see whether Color Quickdraw is available.
* If Color Quickdraw is available, it scans all
connected devices to see what the pixel depth of each
device is. The sole purpose for this is for multiple
monitor setups where one monitor is set for multi-bit
color and another monitor is set for single bit B&W.
* The Rect that surrounds all connected monitors is then
retrieved and stored in the global gScreenRect.
All drawing will normally be confined to this Rect.
Actually, the worms are allowed to extend past these
boundaries by about 4 pixels in each direction. This
allows the worms appear to move more smoothly when they
encounter the edge of the screen.
* A check is then made to see whether a multiple monitor
setup is being used. If so, and if one of the monitors
is set for one bit B&W while another is set for multi-bit
color or grey scale, a new Rect will be stored in the
global gScreenRect. This will restrict the drawing
to the the screen that can display the most colors. This
Rect is scanned for at the same time as the depths of the
monitors is checked.
* Next, a check is made to see whether the Mac Portable is
being used and a flag is set if so. This is done by
checking to see if a pixel on the screen is turned on or
off. The Mac Portable's screen is cleared to white in
order to save the screen, unlike the usual black. The
Sunset INIT handles clearing the screen before the saver
module is called. So if the pixel is white, the portable
is being used. (Any pixel on the screen can be used
as the check, but there are two things to remember. One,
not all screens are the same size so the pixel checked
cannot be very large. Two, if you use the debugger in
Think C, you'll want to check a pixel that isn't on the
Menu Bar - otherwise, while debugging, the module may
think that the portable is being used, when in fact, it
isn't. Checking the pixel at 100,100 is a good way to
get around both of the above problems.)
* PenNormal() is then called to make sure that the drawing
window is set up correctly. This is done because Sunset
allows for different modules to be called between the time
the Macintosh is "put to sleep" and the time it is
"awakened". We need to make sure that a previous module
has not altered the attributes of the window.
* Last, the ForeColor & BackColor are set properly for the
same reasons as above. Then, all screens are cleared to
black.
* Note: The Sunset INIT handled whether the MacPortable was
being used. All references to the the MacPortable were
taken out of the application version of WormMucking.
\******************************************************************/
InitializeScreens()
{
GrafPtr oldPort;
SysEnvRec theWorld;
GDHandle gDevice, oldDevice;
GrafPtr viewScreen;
CGrafPtr workArea;
CGrafPort workPort;
Boolean active, screen;
int depth, gDepth=0, minDepth=1024, maxDepth=1;
Rect gRect;
GetPort( &oldPort );
if ( SysEnvirons( 1, &theWorld ) == envNotPresent )
gColorQD = false;
else
gColorQD = (theWorld.hasColorQD);
if ( gColorQD )
{
oldDevice = GetGDevice();
gDevice = GetDeviceList();
while (gDevice != cNilPointer)
{
screen = TestDeviceAttribute(gDevice, screenDevice);
active = TestDeviceAttribute(gDevice, screenActive);
if (screen AND active)
{
SetGDevice(gDevice);
workArea = &workPort;
OpenCPort( workArea );
depth = (**(*workArea).portPixMap).pixelSize;
if ( depth < minDepth )
minDepth = depth;
if ( depth > maxDepth )
maxDepth = depth;
if ( depth > gDepth )
{
gRect = (**gDevice).gdRect;
gDepth = depth;
}
CloseCPort( workArea );
}
gDevice = GetNextDevice(gDevice);
}
SetGDevice( oldDevice );
}
SetPort( oldPort );
GetPort( &(gViewScreen) );
gScreenRect = gViewScreen->portRect;
gScreenDepth = maxDepth;
if ( minDepth == 1 AND maxDepth > 1 )
gScreenRect = gRect;
PenNormal();
ForeColor( whiteColor );
BackColor( blackColor );
EraseRect( &gScreenRect );
}/* InitializeScreens */
/************************ InitializeWorms *************************\
InitializeWorms() calculates the initial starting position
for each worm. All worms start at the bottom of the screen
and have an equal horizontal distance between them.
\******************************************************************/
InitializeWorms()
{
int left, top, right, bottom;
int i, temp;
left = gScreenRect.left;
top = gScreenRect.top;
right = gScreenRect.right;
bottom = gScreenRect.bottom;
temp = (right-left)/gWormTally+1;
for ( i = 0; i <= gWormTally; i++ )
{
SetRect( &gWormTrail[i][0], temp*i, bottom-4, temp*i+4, bottom );
gV[i] = -3;
gH[i] = 0;
}
}/* InitializeWorms */
/*********************** DrawDefaultItem **************************\
DrawDefaultItem() is used to draw the thick black border around
the default item in a dialog box. The default item is usually
the "OK" button. There is a UserItem which surrounds the OK
button in the DITL of the .rsrc file. ConfigureSaver assigns
a pointer to the routine (DrawDefaultItem) to that UserItem.
Then, whenever the dialog manager goes to update the configure
dialog, it calls this routine to draw the UserItem. Our
UserItem routine then, draws the default button outline using
the Rect of the UserItem - which surrounds the OK button.
\******************************************************************/
pascal void DrawDefaultItem( dialog, item )
DialogPtr dialog;
int item;
{
int itemType;
Rect itemRect;
Handle itemHandle;
GetDItem( dialog, item, &itemType, &itemHandle, &itemRect );
PenSize( 3, 3 );
FrameRoundRect( &itemRect, 16, 16 );
PenSize( 1, 1 );
}/* DrawDefaultItem */
/************************ ConfigureSaver **************************\
ConfigureSaver() first gets the dialog from the resource
file. The dialog is then centered on the screen (over the
Control Panel) with the CenterDialog() routine.
ConfigureSaver() finds whether Color Quickdraw is available
and the deepest device used. It then reads in the customizable
options from the resource file.
ConfigureSaver() then uses this information to adjust
the dialog. All customized values are set and parts of
the dialog are adjusted if color quickdraw is not available.
A pointer to DrawDefaultItem is then assigned to the UserItem
which surrounds the OK button. This will allow the default
button outline to be drawn automatically for us by the
dialog manager.
It stays in the loop until the "OK" or "Cancel" buttons
are pressed. The Cancel button does nothing. OK checks to
see whether values given by the user are "legal" and adjusts
them if not. It then writes all values back out to the
resource file.
InitializeSaver() will then be able to read these values
in and adjust the screen saver accordingly.
\******************************************************************/
ConfigureSaver()
{
int itemType, itemHit, dialogDone = false, screenDepth;
int wormTally=15, wormLength=15, multiColored=0, wormLinks=0;
Rect itemRect;
Handle itemHandle;
StringHandle tempHandle;
Str255 tempString;
long temp;
DialogPtr dialog;
intH modifier;
SysEnvRec theWorld;
Boolean colorQD;
GDHandle maxDevice, oldDevice;
GrafPtr viewScreen;
CGrafPtr workArea;
CGrafPort workPort;
SetPort( FrontWindow() );
dialog = GetNewDialog( cBaseResID, cNilPointer, cMoveToFront );
CenterDialog( dialog );
if ( SysEnvirons( 1, &theWorld ) == envNotPresent )
colorQD = false;
else
colorQD = (theWorld.hasColorQD);
if ( colorQD )
{
GetWMgrPort( &viewScreen );
maxDevice = GetMaxDevice( &(viewScreen->portRect) );
oldDevice = GetGDevice();
SetGDevice( maxDevice );
workArea = &workPort;
OpenCPort( workArea );
screenDepth = (**(*workArea).portPixMap).pixelSize;
SetGDevice( oldDevice );
CloseCPort( workArea );
}
modifier = (intH)GetResource( 'mods', cTallyNum );
temp = (**modifier);
if ( temp >= 1 AND temp < cMax-1 )
wormTally = temp;
modifier = (intH)GetResource( 'mods', cLengthNum );
temp = (**modifier);
if ( temp >= 1 AND temp < cMax-1 )
wormLength = temp;
modifier = (intH)GetResource( 'mods', cMultiNum );
temp = (**modifier);
if ( temp >= 0 AND temp <= 1 )
multiColored = temp;
modifier = (intH)GetResource( 'mods', cLinksNum );
temp = (**modifier);
if ( temp >= 0 AND temp <= 1 )
wormLinks = temp;
GetDItem( dialog, cWormLength, &itemType, &itemHandle, &itemRect );
SelIText( dialog, cWormLength, 0, 32767 );
NumToString( (long)wormLength, tempString );
SetIText( itemHandle, tempString );
SelIText( dialog, cWormLength, 0, 0);
GetDItem( dialog, cwormTally, &itemType, &itemHandle, &itemRect );
SelIText( dialog, cwormTally, 0, 32767 );
NumToString( (long)wormTally, tempString );
SetIText( itemHandle, tempString );
SelIText( dialog, cwormTally, 0, 32767 );
GetDItem( dialog, cMultiColored, &itemType, &itemHandle, &itemRect );
SetCtlValue( itemHandle, multiColored );
if ( !colorQD OR screenDepth == 1 )
{
SetCtlValue( itemHandle, false );
HiliteControl( (ControlHandle)itemHandle, 255 );
}
GetDItem( dialog, cWormLinks, &itemType, &itemHandle, &itemRect );
SetCtlValue( itemHandle, wormLinks );
/* The following assigns DrawDefaultItem to the cUserItem dialog Item */
GetDItem( dialog, cUserItem, &itemType, &itemHandle, &itemRect );
SetDItem( dialog, cUserItem, itemType, (Handle)&DrawDefaultItem, &itemRect);
ShowWindow( dialog );
while ( dialogDone == false )
{
ModalDialog( cNilPointer, &itemHit );
switch ( itemHit )
{
case cMultiColored:
GetDItem( dialog, cMultiColored, &itemType, &itemHandle, &itemRect );
temp = GetCtlValue( itemHandle );
SetCtlValue( itemHandle, !temp );
break;
case cWormLinks:
GetDItem( dialog, cWormLinks, &itemType, &itemHandle, &itemRect );
temp = GetCtlValue( itemHandle );
SetCtlValue( itemHandle, !temp );
break;
case cOK:
HideWindow( dialog );
dialogDone = true;
GetDItem( dialog, cwormTally, &itemType, &itemHandle, &itemRect );
GetIText( itemHandle, &tempString );
StringToNum( tempString, &temp );
if ( temp >= 1 AND temp < cMax-1 )
wormTally = temp;
if ( temp > cMax-2 )
wormTally = cMax-2;
if ( temp < 1 )
wormTally = 1;
GetDItem( dialog, cWormLength, &itemType, &itemHandle, &itemRect );
GetIText( itemHandle, &tempString );
StringToNum( tempString, &temp );
if ( temp >= 1 AND temp < cMax-1 )
wormLength = temp;
if ( temp > cMax-2 )
wormLength = cMax-2;
if ( temp < 1 )
wormLength = 1;
GetDItem( dialog, cMultiColored, &itemType, &itemHandle, &itemRect );
multiColored = GetCtlValue( itemHandle );
GetDItem( dialog, cWormLinks, &itemType, &itemHandle, &itemRect );
wormLinks = GetCtlValue( itemHandle );
modifier = (intH)GetResource( 'mods', cTallyNum );
(**modifier) = wormTally;
ChangedResource( (Handle)modifier );
WriteResource( (Handle)modifier );
modifier = (intH)GetResource( 'mods', cLengthNum );
(**modifier) = wormLength;
ChangedResource( (Handle)modifier );
WriteResource( (Handle)modifier );
modifier = (intH)GetResource( 'mods', cMultiNum );
(**modifier) = multiColored;
ChangedResource( (Handle)modifier );
WriteResource( (Handle)modifier );
modifier = (intH)GetResource( 'mods', cLinksNum );
(**modifier) = wormLinks;
ChangedResource( (Handle)modifier );
WriteResource( (Handle)modifier );
break;
case cCancel:
HideWindow( dialog );
dialogDone = true;
break;
}
}
DisposDialog( dialog );
}/* ConfigureSaver */
/************************* CenterDialog ***************************\
CenterDialog() is a routine which will center a given dialog
over the main screen.
To use it, pass CenterDialog() a pointer to your dialog.
\******************************************************************/
CenterDialog (adialog)
DialogPtr adialog;
{
int xOffset, yOffset, dwidth, dheight;
Rect arect;
arect = (*adialog).portRect;
dwidth = arect.right - arect.left;
dheight = arect.bottom - arect.top;
xOffset=((screenBits.bounds.right-screenBits.bounds.left)-dwidth) / 2;
yOffset=((screenBits.bounds.bottom -screenBits.bounds.top)-dheight) / 2;
MoveWindow(adialog, xOffset, yOffset, false);
}/* CenterDialog */
/************************** MoveWorms *****************************\
MoveWorms() moves each worm a single "link". Calling it
once each time we receive the RUN message provides for our
single frame of animation per RUN message.
It starts by calculating where the next link will be placed.
The incentive[] values are used to turn the worm around when
it hits one of the edges of the screen. The worm is given
an "incentive" to move in the opposite direction. The correct
color for the worm is then determined and the link is drawn.
\******************************************************************/
MoveWorms()
{
int h, v, ii;
long i;
/* Calculate the new position for each worm. */
for ( ii=0; ii<=gWormTally-1; ii++ )
{
v = gV[ii];
h = gH[ii];
if ( v == gMoveMax OR v == -(gMoveMax) )
{
i = Randomize(2);
if ( gIncentive[ii].h > 0 )
{
i = 1;
gIncentive[ii].h -= 1;
}
else if ( gIncentive[ii].h < 0 )
{
i = 0;
gIncentive[ii].h += 1;
}
switch (i)
{
case cNegative:
h -= 1;
if ( h < -(gMoveMax) )
h = -(gMoveMax);
break;
case cPositive:
h += 1;
if ( h > (gMoveMax) )
h = (gMoveMax);
break;
}
}
if ( h == gMoveMax OR h == -(gMoveMax) )
{
i = Randomize(2);
if ( gIncentive[ii].v > 0 )
{
i = 1;
gIncentive[ii].v -= 1;
}
else if ( gIncentive[ii].v < 0 )
{
i = 0;
gIncentive[ii].v += 1;
}
switch (i)
{
case cNegative:
v -= 1;
if ( v < -(gMoveMax) )
v = -(gMoveMax);
break;
case cPositive:
v += 1;
if ( v > (gMoveMax) )
v = (gMoveMax);
break;
}
}
gWormRect[ii] = gWormTrail[ii][0];
OffsetRect( &gWormRect[ii], h, v );
/*
Now determine if the worm has hit one of the sides. If it has, back it
off and give it "incentive" to move in the other direction.
*/
if ( gWormRect[ii].left > gScreenRect.right+4 )
{
OffsetRect( &gWormRect[ii], -h, 0 );
gIncentive[ii].h = -10;
}
if ( gWormRect[ii].right < gScreenRect.left-4 )
{
OffsetRect( &gWormRect[ii], -h, 0 );
gIncentive[ii].h = 10;
}
if ( gWormRect[ii].bottom < gScreenRect.top-4 )
{
OffsetRect( &gWormRect[ii], 0, -v );
gIncentive[ii].v = 10;
}
if ( gWormRect[ii].top > gScreenRect.bottom+4 )
{
OffsetRect( &gWormRect[ii], 0, -v );
gIncentive[ii].v = -10;
}
gV[ii] = v;
gH[ii] = h;
/* Determine the correct color for the worm and draw the new link. */
if ( gMultiColored AND gColorQD AND gScreenDepth > 3 )
CalcRGBColor();
ForeColor(whiteColor);
if ( gScreenDepth > 3 AND gColorQD )
{
if ( !gMultiColored )
RGBForeColor( &gSpawnColor[ii] );
if ( gMultiColored )
RGBForeColor( &gWormColor );
}
if ( gWormLinks )
FrameOval( &gWormRect[ii] );
else
PaintOval( &gWormRect[ii] );
}
} /* MoveWorms */
/************************ CalcRGBColor ****************************\
CalcRGBColor() is used to calculate a random RGB color for the
worms. RGB colors range from 0 to about 65000 for each part
of the color (R, G, B). We call Randomize() twice for each
part of the RGBColor getting a random number between 0 and
30000 each time and adding them together. We must do this
twice since Randomize() will only accept values up to 32768.
We end up with a value from 0-60000. We then add 5000
to each to assure that the random RGBColor we return is
bright enough to be visible.
After this, we do another random check and adjust the values
of one or two parts of the RGBColor down to 1000. What this
does is to make brighter, more colorful worms. Without it, the
worms are a kind of dull pastel.
\******************************************************************/
CalcRGBColor()
{
int i;
i = Randomize( 30000 );
gWormColor.red = i;
i = Randomize( 30000 );
gWormColor.red += (i+5000);
i = Randomize( 30000 );
gWormColor.green = i;
i = Randomize( 30000 );
gWormColor.green += (i+5000);
i = Randomize( 30000 );
gWormColor.blue = i;
i = Randomize( 30000 );
gWormColor.blue += (i+5000);
i = Randomize(6);
if ( i==0 )
gWormColor.red = 1000;
if ( i==1 )
gWormColor.green = 1000;
if ( i==2 )
gWormColor.blue = 1000;
if ( i==3 )
{
gWormColor.red = 1000;
gWormColor.green = 1000;
}
if ( i==4 )
{
gWormColor.red = 1000;
gWormColor.blue = 1000;
}
if ( i==5 )
{
gWormColor.blue = 1000;
gWormColor.green = 1000;
}
}/* CalcRGBColor */
/************************** Randomize *****************************\
Randomize() takes a number from 0-32768 and returns a random
number within the range given.
\******************************************************************/
Randomize( range )
int range;
{
long rawResult;
rawResult = Random();
if ( rawResult < 0 )
rawResult *= -1;
return( (rawResult * range) / 32768 );
}/* Randomize */
/***************************** Wait *******************************\
This procedure is not actually used by WormMucking. It is
provided here for possible assistance with other modules you
may write. In some of the modules, it is desirable to have
the animation appear at a specified speed. Sunset provides
for this in general by timing the calls to modules once every
60th of a second. If you need to slow down your own animation,
or want a piece of animation to run the same speed on different
machines, this routine can come in handy. To use it, set up
these #defines at the beginning of the module:
#define cSeconds 60
#define cTicks 1
Then when you wish to delay the animation, make a call to
this routine. A call might look like:
Wait( 30, cTicks ); ....or....
Wait( 3, cSeconds ); ...etc....
A "tick" is 1/60th of a second. An example of a module
that uses this routine is TGMBA. After the
lightning is displayed on the screen, this routine is
called to delay erasing the screen for approximately
1/2 second, or 30 ticks.
\******************************************************************/
Wait(length, unit)
int length, unit;
{
long ticks, timer;
length *= unit;
ticks = TickCount();
timer = ticks+length;
while ( timer > ticks )
ticks = TickCount();
}/* Wait */
/*
This is the end of the source code to Tutorial.c. Below is a
description of the resources in Tutorial.╣.rsrc and then a
discussion of the animation techniques used by several of the
Sunset modules.
*/
/************************* Tutorial.╣.rsrc ************************\
The Tutorial.╣.rsrc file contains five types of resources.
Actually, a resource file is not absolutely necessary in
order to make a screen saver. The dialog box for the customize
options can be created from within the screen saver itself.
Most of the other resources, such as PICTs and vers, are only
used to add completeness to the saver - not because they have
to be there. Working with a resource file is a bit easier
however and also adds more flexibility.
The five types of resources in this resource file are:
DITL - A Dialog ITem List for all the items that appear
within the customize dialog box. All of the radio
buttons, checkboxes, pictures, etc, go in here.
DLOG - A Dialog box that references the above DITL. This
is the dialog box that is called and opened in the
CustomizeSaver() routine.
mods - mods is a custom resource type used to hold the
users requests from CustomizeSaver(). There are
four within this resource file. It is through these
resources that the saver communicates from the
ConfigureSaver() routine to the rest of the saver
the user's wishes regarding modifying what the saver
does. The four mods resources in this file are:
WormTally: Number of worms to draw.
WormLength: Length of worms.
MultiColored: Whether each link of the worm is a
different color.
WormLinks: Whether to draw solid sections of
worms or "links".
PICT - The PICTs used in the WormMucking saver are only
used within the customize dialog box to give it a
better look. Some of the other savers included
with Sunset contain PICTs that are used for
animation within the saver. For example, Space
Duel contains PICTs of two ships that are animated.
Another common type of resource used for animation
is the ICON resource.
vers - There are two vers resources usually included.
They are used to display information about the
saver in the Get Info box from the Finder. One is
used to display the actual version number of the
saver. The other is used to display the
copyright.
\******************************************************************/
/********************** Animation Techniques **********************\
The main thing to remember in animating is to try to always
keep what you're animating on the screen. If you have
a choice (you won't always have one), it is better to
Draw New/Erase Old than to Erase Old/Draw New. The
reason is that whenever the object of your animation
is off the screen, no matter how short the period, the
object will flicker. Sometimes this doesn't matter,
or cannot be avoided.
There are many ways of doing animation. The best method
to use can vary depending on the circumstances. Below
is a brief discussion of several of the modules, what
animation techniques they used, and why.
WormMucking: The WormMucking module uses the Draw New/Erase
Old technique for its animation. For each frame of
animation, the module goes through and moves each worm one
link. It first calculates the new position for the next link
in the worm. After drawing this link, it erases the last
link in the worm. Then it shifts the logical location of each
link to reflect their new position in the worm "chain". During
each frame of animation, this process is done once for each
worm. This is pretty much a classic use of the Draw New/Erase
Old method of animation. Looking at the worms move, you'll
notice that each link is separate from the link immediately
before it and doesn't overlap at all. This is one of the
reasons the Draw New/Erase Old method is used. If the new
link overlapped the previous link, then when the previous link
was erased, it would also erase part of the new link. (This
condition is why the Billiards module uses the Erase Old/Draw
New method of drawing.) So one of the main conditions of the
Draw New/Erase Old method is (usually) that the new object not
overlap the old one.
(While the new link doesn't overlap the link immediately
before it, it can overlap other links or other worms. As
it works out though, the worms go fast enough so that this
effect is hardly noticeable. Would it be worth it to make
the worms either not overlap at all or not erase part of a
"good" link? There exists a version of WormMucking that
does just that. However, keeping track of all that
information slows the module down to a crawl. So the
tradeoff in this case is to let a little overlap/erase
happen for the sake of speed and multiple worms.)
\******************************************************************/